'''
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with self work for additional information
 * regarding copyright ownership.  The ASF licenses self file
 * to you under the Apache License, Version 2.0 (the
 * "License") you may not use self file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http:#www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
'''


#import java.lang.reflect.Constructor;
#import java.lang.reflect.Method;
#import java.util.ArrayList;
#import java.util.HashSet;
#import java.util.List;
#import java.util.Set;

#import org.apache.jena.assembler.assemblers.AssemblerGroup;
#import org.apache.jena.assembler.exceptions.AmbiguousSpecificTypeException;
#import org.apache.jena.atlas.logging.Log;
#import org.apache.jena.datatypes.xsd.XSDDatatype;
#import org.apache.jena.rdf.model.*;
#import org.apache.jena.shared.BadDescriptionMultipleRootsException;
#import org.apache.jena.shared.BadDescriptionNoRootException;
#import org.apache.jena.shared.JenaException;
#import org.apache.jena.shared.PrefixMapping;
#import org.apache.jena.vocabulary.RDF;
#import org.apache.jena.vocabulary.RDFS;

'''
 * AssemblerHelp provides utility methods used by, and useful for working with,
 * the Assembler code, including the methods that expand a model to include the
 * required inferences and to find the most specific type of a root in an
 * assembler specification.
'''
AssemblerHelp
    '''
     * A useful constant for <code>listStatements(S, P, O)</code>.
    '''
    protected static final Property ANY = null

    '''
     * Answer a Resource .equals() to <code>root</code>, but in the expanded
     * model.
    '''
    # param Resource root
    # result static Resource
    def withFullModel(self, root):
        return root.inModel(fullModel(root.getModel()))
    

    '''
     * Answer the full model of <code>m</code>, with all its imports included and
     * with the necessary properties added from the JA schema. However, if
     * the magic footprint triple (ja:self, rdf:type, ja:Expanded) is present in the
     * model, it is returned unchanged. Imports are managed by the shared
     * <code>ImportManager.instance</code>.
    '''
    # param Model m
    # result static Model
    def fullModel(self, m):
        return fullModel(ImportManager.instance, m)
    

    '''
     * Answer the full model of <code>m</code>, with all its imports included and
     * with the necessary properties added from the JA schema. However, if
     * the magic footprint triple (ja:self, rdf:type, ja:Expanded) is present in the
     * model, it is returned unchanged. Imports are managed by <code>im</code>.
    '''
    # param ImportManager im, Model m
    # result static Model
    def fullModel(self, im, m):
        return m.contains(JA.This, RDF.type, JA.Expanded)
                ? m
                : (Model) ModelExpansion.withSchema(im.withImports(m), JA.getSchema())
                .add(JA.This, RDF.type, JA.Expanded)
                .setNsPrefixes(PrefixMapping.Extended)
                .setNsPrefixes(m)
                
    

    '''
     * Load all the classes which are objects of any (t, ja:loadClass, S)
     * statements in <code>m</code>. The order in which the classes are
     * loaded is not specified, and loading stops immediately if any class
     * cannot be loaded.
     * <p>
     * Contrast with <code>loadClasses(AssemblerGroup,Model)</code>,
     * which loads classes and assumes that those classes are assemblers to
     * be added to the group.
    '''
    # param AssemblerGroup g, Model m
    # result static void
    def loadArbitraryClasses(self, g, m):
        StmtIterator it = m.listStatements(null, JA.loadClass, ANY)
        while (it.hasNext()) loadArbitraryClass(g, it.nextStatement())
    

    '''
     * Load all the classes which are objects of any (t, ja:assembler, S) statements
     * in <code>m</code>. <code>group.implementWIth(t,c)</code> is called
     * for each statement, where <code>c</code> is an instance of the class named
     * by <code>S</code>. The order in which the classes are loaded is not
     * specified, and loading stops immediately if any class cannot be loaded.
    '''
    # param AssemblerGroup group, Model m
    # result static void
    def loadAssemblerClasses(self, group, m):
        StmtIterator it = m.listStatements(ANY, JA.assembler, ANY)
        while (it.hasNext()) loadAssemblerClass(group, it.nextStatement())
    

    '''
     * Load the class named by the object of <code>s</code>, run its
     * <code>whenRequiredByAssembler</code> method if any, and
     * register an <code>implementWith</code> for the subject of
     * <code>s</code> and an instance of the class.
    '''
    # param AssemblerGroup group, Statement s
    # result static void
    def loadAssemblerClass(self, group, s):
        Class<?> c = loadArbitraryClass(group, s)
        runAnyAssemblerConstructor(group, s, c)
    

    '''
     * Load the class named by the object of <code>s</code> if necessary.
     * If that class has a static method <code>whenRequiredByAssembler</code>
     * with an <code>AssemblerGroup</code> argument, call that method
     * passing it <code>ag</code>.
    '''
    # param AssemblerGroup ag, Statement s
    # result static Class<?>
    def loadArbitraryClass(self, ag, s):
        Class<?> loaded = loadClassNamedBy(s)
        try:
            Method m = loaded.getDeclaredMethod("whenRequiredByAssembler", Class[]:AssemblerGroup.class)
            m.invoke(null, ag)
        catch (NoSuchMethodException e): ''' that's OK''' catch (Exception e):
            raise JenaException(e)
        
        return loaded
    

    # param Statement s
    # result static Class<?>
    def loadClassNamedBy(self, s):
        String x = getString(s)
        # Jena2 -> Jena3 transition 
        if (x.startsWith("com.hp.hpl.jena")):
            String x1 = x.replaceFirst("com.hp.hpl.jena", "org.apache.jena")
            Log.warnOnce(AssemblerHelp.class, "ja:loadClass: Migration to Jena3: Converting " + x + " to " + x1, x)
            x = x1
        
        try:
            return Class.forName(x)
        catch (Exception e):
            raise JenaException(e)
        
    

    # param AssemblerGroup group, Statement s, Class<?> c
    # result static void
    def runAnyAssemblerConstructor(self, group, s, c):
        try:
            Resource type = s.getSubject()
            Constructor<?> con = getResourcedConstructor(c)
            if (con is None):
                establish(group, type, c.getConstructor().newInstance())
            else
                establish(group, type, con.newInstance(s.getSubject()))
        catch (Exception e):
            raise JenaException(e)
        
    

    # param AssemblerGroup group, Resource type, Object x
    # result static void
    def establish(self, group, type, x):
        if (x instanceof Assembler):
            group.implementWith(type, (Assembler) x)
        else
            raise JenaException("constructed entity is not an Assembler: " + x)
    

    # param Class<?> c
    # result static Constructor<?>
    def getResourcedConstructor(self, c):
        try:
            return c.getConstructor(new Class[]:Resource.class)
        catch (SecurityException e):
            return null
        catch (NoSuchMethodException e):
            return null
        
    

    '''
     * Answer the most specific type of <code>root</code> that is a subclass of
     * ja:Object. If there are no candidate types, answer <code>givenType</code>.
     * If there is more than one type, throw a NoSpecificTypeException.
    '''
    # param Resource root
    # result static Resource
    def findSpecificType(self, root):
        return findSpecificType(root, JA.Object)
    

    '''
     * Answer the most specific type of <code>root</code> that is a subclass of
     * <code>givenType</code>. If there are no candidate types, answer
     * <code>givenType</code>. If there is more than one type, throw a
     * NoSpecificTypeException.
    '''
    # param Resource root, Resource baseType
    # result static Resource
    def findSpecificType(self, root, baseType):
        Set<Resource> types = findSpecificTypes(root, baseType)
        if (types.size() == 1):
            return types.iterator().next()
        if (types.size() == 0):
            return baseType
        raise AmbiguousSpecificTypeException(root, ArrayList<>(types))
    

    '''
     * Answer all the types of <code>root</code> which are subtypes of
     * <code>baseType</code> and which do not have subtypes which are
     * also types of <code>root</code>.
    '''
    # param Resource root, Resource baseType
    # result static Set<Resource>
    def findSpecificTypes(self, root, baseType):
        List<RDFNode> types = root.listProperties(RDF.type).mapWith(Statement::getObject).toList()
        Set<Resource> results = HashSet<>()
        for (int i = 0 i < types.size() i += 1):
            Resource candidate = (Resource) types.get(i)
            if (candidate.hasProperty(RDFS.subClassOf, baseType)):
                if (hasNoCompetingSubclass(types, candidate)):
                    results.add(candidate)
        
        return results
    

    # param List<RDFNode> types, Resource candidate
    # result static boolean
    def hasNoCompetingSubclass(self, types, candidate):
        for (RDFNode type : types):
            Resource other = (Resource) type
            if (other.hasProperty(RDFS.subClassOf, candidate) and !candidate.equals(other)):
                return False
            
        
        return True
    

    '''
     * Answer the resource that is the object of the statement <code>s</code>. If
     * the object is not a resource, throw a BadObjectException with that statement.
    '''
    # param Statement s
    # result static Resource
    def getResource(self, s):
        RDFNode ob = s.getObject()
        if (ob.isLiteral()):
        raise BadObjectException(s)
        return (Resource) ob
    

    '''
     * Answer the plain string object of the statement <code>s</code>. If the
     * object is not a string literal, throw a BadObjectException with that statement.
    '''
    # param Statement s
    # result static String
    def getString(self, s):
        RDFNode ob = s.getObject()
        if (ob.isResource()):
        raise BadObjectException(s)
        Literal L = (Literal) ob
        if (not L.getLanguage().equals("")):
        raise BadObjectException(s)
        if (L.getDatatype() is None):
            return L.getLexicalForm()
        if (L.getDatatype() == XSDDatatypeInstances.XSDstring):
            return L.getLexicalForm()
        raise BadObjectException(s)
    

    '''
     * Answer the String value of the literal <code>L</code>, which is the
     * object of the Statement <code>s</code>. If the literal is not an
     * XSD String or a plain string without a language code, throw a
     * BadObjectException.
    '''
    # param Statement s, Literal L
    # result static String
    def getString(self, s, L):
        if (not L.getLanguage().equals("")):
        raise BadObjectException(s)
        if (L.getDatatype() is None):
            return L.getLexicalForm()
        if (L.getDatatype() == XSDDatatypeInstances.XSDstring):
            return L.getLexicalForm()
        raise BadObjectException(s)
    

    '''
     * Answer a Set of the ja:Object resources in the full expansion of
     * the assembler specification model <code>model</code>.
    '''
    # param Model model
    # result static Set<Resource>
    def findAssemblerRoots(self, model):
        return findAssemblerRoots(model, JA.Object)
    

    '''
     * Answer a Set of the objects in the full expansion of the assembler
     * specification <code>model</code> which have rdf:type <code>type</code>,
     * which <i>must</i> be a subtype of <code>ja:Object</code>.
    '''
    # param Model model, Resource type
    # result static Set<Resource>
    def findAssemblerRoots(self, model, type):
        return fullModel(model).listResourcesWithProperty(RDF.type, type).toSet()
    

    '''
     * Answer the single resource in <code>singleRoot</code> of type
     * <code>ja:Model</code>. Otherwise throw an exception.
    '''
    # param Model singleRoot
    # result static Resource
    def singleModelRoot(self, singleRoot):
        return singleRoot(singleRoot, JA.Model)
    

    '''
     * Answer the single resource in <code>singleRoot</code> of type
     * <code>type</code>. Otherwise throw an exception.
    '''
    # param Model singleRoot, Resource type
    # result static Resource
    def singleRoot(self, singleRoot, type):
        Set<Resource> roots = findAssemblerRoots(singleRoot, type)
        if (roots.size() == 1):
            return roots.iterator().next()
        if (roots.size() == 0):
        raise BadDescriptionNoRootException(singleRoot, type)
        raise BadDescriptionMultipleRootsException(singleRoot, type)
    

